package controllers

import (
	"fmt"
	"ginstudydemo.com/main/models"
	"ginstudydemo.com/main/service"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/copier"
	"golang.org/x/crypto/bcrypt"
	"time"
)

type IAuthController interface {
	LoginHandler()
	RefreshTokenHandler()
	LogoutHandler()
}

type AuthController struct{}

func NewAuthController() *AuthController {
	return &AuthController{}
}

// LoginHandler  登录功能
func (u AuthController) LoginHandler(c *gin.Context) {

	//使用数据表结构体获取参数
	param := &models.LoginRequest{}
	// 把结构体指针传入BindJSON，这样param的值才能同步修改
	cErr := c.BindJSON(param)

	if !(cErr == nil) {
		service.ReturnErr(c, "4010", "获取参数错误")
		c.Abort()
		return
	}

	// 如果没有设备 ID，则生成一个默认设备标识
	if param.DeviceID == "" {
		param.DeviceID = fmt.Sprintf("device-%d", time.Now().UnixNano())
	}

	//TODO: 验证表单信息  1. 用户名邮箱手机号必须有一个有值  2.邮箱格式 手机号格式

	//TODO:  数据库验证手机号是存在（目前先实现手机登录）或 邮箱是存在 然后取出密码 model中实现 或者以后加 service model制作查询 service封装model查询的方法

	modelUserInfo, modelErr := models.GetVerifyUserInfo(*param)
	if !(modelErr == nil) {
		service.ReturnErr(c, "4003", "账号错误")
		c.Abort()
		return
	}
	fmt.Println(modelUserInfo)

	//判断输入的明文密码和数据库中的加密密码是否匹配
	/**
	bcrypt.CompareHashAndPassword 是 bcrypt 包中的一个函数，用于比较已加密的哈希密码和未加密的明文密码，返回错误信息来表示密码是否匹配。
	第一个参数是加密过的密码（哈希值）。
	第二个参数是用户输入的明文密码。
	#[]byte(modelUserInfo.Password)
	如果哈希密码和明文密码匹配，则返回 nil，表示验证通过；如果不匹配，则返回一个错误，通常会进一步处理错误来通知用户密码错误。
	modelUserInfo.Password 是数据库中存储的用户的哈希密码。由于 bcrypt.CompareHashAndPassword 期望输入的密码是字节数组（[]byte），所以需要将密码从字符串转换为字节数组。
	假设 modelUserInfo 是从数据库中查询得到的用户信息结构体，它的 Password 字段存储的是已经使用 bcrypt 加密后的哈希密码。
	#[]byte((*param).Password)
	(*param).Password 是用户通过登录表单提交的明文密码。这里 param 是一个指针，指向包含用户输入数据的结构体，其中 Password 是用户输入的未加密密码。
	和 modelUserInfo.Password 一样，我们需要将明文密码转换为字节数组，以便与加密的哈希密码进行比较。
	*/
	bcryptErr := bcrypt.CompareHashAndPassword([]byte(modelUserInfo.Password), []byte((*param).Password))
	if bcryptErr != nil {
		service.ReturnErr(c, "9999", "密码不正确")
		c.Abort()
		return
	}

	// 使用 copier 复制结构体 .UserWithRole  到  SysUserUsersLoginTokenUserInfo
	var tokenUserInfo models.SysUserUsersLoginTokenUserInfo
	tokenUserInfo.DeviceID = param.DeviceID //增加设备id
	cpErr := copier.Copy(&tokenUserInfo, &modelUserInfo)
	if cpErr != nil {
		service.ReturnErr(c, "9999", "未知错误1")
		c.Abort()
		return
	}

	// 生成一个带有自定义声明（claims）的 JWT（JSON Web Token）令牌。
	token, tkErr := service.NewJwtV5().EncryptUserToken(tokenUserInfo)
	if tkErr != nil {
		service.ReturnErr(c, "9999", "token生成失败")
		c.Abort()
		return
	}

	// 生成刷新token
	refreshToken, tkErr := service.NewJwtV5().EncryptRefreshToken(tokenUserInfo)
	if tkErr != nil {
		service.ReturnErr(c, "9999", "token生成失败")
		c.Abort()
		return
	}

	// 设置刷新 token 为 HTTP Only 的 Cookie
	c.SetCookie("refresh_token", refreshToken, 7*24*3600, "/", "", false, true) // 设置为 7 天有效期，Secure 和 HttpOnly 属性

	// TODO:更新最后登录时间
	//user.LastLoginTime = time.Now()
	//models.DB.Save(&user)

	//返回结果
	service.ReturnSuccess[service.JwtTokens](c, "2000", "用户ok", service.JwtTokens{
		AccessToken: token,
	})
}

// RefreshTokenHandler 通过刷新token获取新的token
func (u AuthController) RefreshTokenHandler(c *gin.Context) {
	//从header获取刷新token
	// 获取 HTTP Only Cookie 中的 refresh_token
	token, err := c.Cookie("refresh_token")
	if err != nil {
		service.ReturnErr(c, "4002", "未提供刷新 Token")
		c.Abort()
		return
	}

	if token == "" {
		service.ReturnErr(c, "4002", "未提供刷新 Token")
		return
	}

	//判断刷新token是否在黑名单，如果在黑名单返回获取刷新token获取失败，前端返回登录页
	// 检查 token 是否在黑名单中
	//isBlacklisted, svErr := service.NewAuthService().IsTokenBlacklisted(token)
	//if svErr != nil {
	//	service.ReturnErr(c, "4002", "RefreshToken 黑名单效验失败")
	//	c.Abort()
	//	return
	//}
	//if isBlacklisted {
	//	service.ReturnErr(c, "4002", "RefreshToken 在黑名单中")
	//	c.Abort()
	//	return
	//}

	// 验证JWT token的有效性
	claims, tokenErr := service.NewJwtV5().DecodeUserToken(token)
	if tokenErr != nil {
		service.ReturnErr(c, "4002", "无效 Refresh token")
		return
	}

	// 验证 Redis 中的 Token
	key := fmt.Sprintf("user:%d:device:%s", claims.UserInfo.UserID, claims.UserInfo.DeviceID)
	bRes, err := service.NewStoredTokenService().VerifyRefreshToken(key, token)
	//storedToken, err := Rdb.Get(context.Background(), key).Result()
	if !bRes {
		service.ReturnErr(c, "4002", "无效 Refresh token")
		return
	}

	// 如果刷新token验证成功，删除旧token
	// 将 token 添加到黑名单，黑名单的过期时间与 token 的过期时间一致
	service.NewStoredTokenService().DelRefreshToken(key)

	// 生成新的 AccessToken 和 RefreshToken
	AccessToken, tkErr := service.NewJwtV5().EncryptUserToken(claims.UserInfo)
	if tkErr != nil {
		service.ReturnErr(c, "4002", tkErr)
		c.Abort()
		return
	}
	refreshToken, tkErr := service.NewJwtV5().EncryptRefreshToken(claims.UserInfo)
	if tkErr != nil {
		service.ReturnErr(c, "4002", tkErr)
		c.Abort()
		return
	}

	// 设置刷新 token 为 HTTP Only 的 Cookie
	c.SetCookie("refresh_token", refreshToken, 7*24*3600, "/", "", false, true) // 设置为 7 天有效期，Secure 和 HttpOnly 属性

	//返回结果
	service.ReturnSuccess[service.JwtTokens](c, "2000", "刷新OK", service.JwtTokens{
		AccessToken: AccessToken,
	})
}

// LogoutHandler 注销登录
func (u AuthController) LogoutHandler(c *gin.Context) {

	// 获取 AccessToken 和 RefreshToken
	accessToken := c.GetHeader("Authorization")
	if accessToken == "" {
		service.ReturnErr(c, "9999", "Authorization token is required")
		return
	}

	refreshToken := c.GetHeader("RefreshToken")
	if refreshToken == "" {
		service.ReturnErr(c, "9999", "Authorization token is required")
		return
	}

	// 验证JWT accessToken 的有效性
	accessTokenClaims, tokenErr := service.NewJwtV5().DecodeUserToken(accessToken)
	if tokenErr != nil {
		service.ReturnErr(c, "9999", "Invalid token")
		return
	}

	// 验证JWT refreshToken的有效性
	refreshTokenClaims, tokenErr := service.NewJwtV5().DecodeUserToken(refreshToken)
	if tokenErr != nil {
		service.ReturnErr(c, "9999", "Invalid token")
		return
	}

	// 将 AccessToken  添加到黑名单，黑名单的过期时间与 token 的过期时间一致
	expiration1 := time.Until(accessTokenClaims.ExpiresAt.Time) // 通过字段名访问
	SvErr := service.NewAuthService().AddTokenToBlacklist(accessToken, expiration1)
	if SvErr != nil {
		service.ReturnErr(c, "9999", "Failed to blacklist token")
		return
	}
	//将 refreshToken  添加到黑名单，黑名单的过期时间与 token 的过期时间一致
	//防止退出登录刷新token作为token使用
	expiration2 := time.Until(refreshTokenClaims.ExpiresAt.Time) // 通过字段名访问
	SvErr2 := service.NewAuthService().AddTokenToBlacklist(refreshToken, expiration2)
	if SvErr2 != nil {
		service.ReturnErr(c, "9999", "Failed to blacklist token")
		return
	}
	// 删除Redis中的刷新token
	key := fmt.Sprintf("user:%d:device:%s", refreshTokenClaims.UserInfo.UserID, refreshTokenClaims.UserInfo.DeviceID)
	service.NewStoredTokenService().DelRefreshToken(key)

	service.ReturnSuccess[string](c, "2000", "Logout successful", "")

}
